﻿using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Language.Scc
{
    internal abstract class Exclusive
    {
        #region Private Data

        private int _lock;
        private Task _task;

        private Task Task => _task ?? Task.CompletedTask;

        #endregion Private Data

        #region Public Properties

        public bool Locked => _lock == 1;

        #endregion Public Properties

        #region Private Methods

        private bool Lock()
        {
            return Interlocked.CompareExchange( ref _lock, 1, 0 ) == 0;
        }

        private void Unlock()
        {
            Debug.Assert( _lock == 1 );

            _lock = 0;
        }

        #endregion Private Methods

        #region Interface

        /// <summary>
        /// Invokes the <see cref="DoLocked"/> function, and ensures it can only be run once
        /// /// </summary>
        /// <returns></returns>
        protected async Task RunLocked( params object[] list )
        {
            if ( !Lock() )
                return;

            _task = DoLocked( list );
            await _task;

            Unlock();
        }

        protected async Task RunLockedWait( params object[] list )
        {
            if( Lock() )
            {
                _task = DoLocked( list );
                await _task;

                Unlock();
            }
            else
            {
                await Task;
            }
        }

        /// <summary>
        /// Implement this function and place your exclusive code here
        /// </summary>
        /// <returns></returns>
        protected abstract Task DoLocked( params object[] list );

        #endregion Interface
    }
}
